
#include <yaffs2_w25n01gv_drv.h>
#include <w25n01gvzeig.h>
#include <yaffs_trace.h>
#include <w25n01gv_drv.h>

#include <nand_store.h>
#include <yaffs_flashif.h>
#include <yaffs_guts.h>
#include <yaffs_ecc.h>

#include <rtthread.h>

static w25n01gv_uint8_t w25n01gv_spare_buf[W25N01GV_SPARE_BYTES_PER_PAGE];

static void w25n01gv_wait_idle(void)
{
    /* step3: wait W25N01GV prepare read data done */
    w25n01gv_uint8_t busy = 1;
    while (1) {
        w2501gv_busy(&busy);
        if (!busy)
            break;
        rt_thread_mdelay(10);
    }
}

/// @brief write page(2048 byte user data + 64 byte spare area)
/// @param dev
/// @param nand_chunk absolute page index insied W25N01GV
/// @param data
/// @param data_len
/// @param oob
/// @param oob_len
/// @return
static int yaffs_w25n01gv_write_chunk(struct yaffs_dev *dev, int nand_chunk,
                                      const u8 *data, int data_len,
                                      const u8 *oob, int oob_len)
{
    if (!data)
        return YAFFS_FAIL;

    /* Calc ECC and marshall the oob bytes into the buffer */
    rt_memset(w25n01gv_spare_buf, 0xff, chip->spare_bytes_per_page);

    u8 *ecc;
    int i;

    for (i = 0, ecc = w25n01gv_spare_buf + 2; i < chip->data_bytes_per_page; i += 256, ecc += 3)
        yaffs_ecc_calc(data + i, ecc);

    /* 26 = 3(ecc size) * 2(one ecc covers 256 byte, one sector has 512 byte) * 4(one page has 4 sector) + 2 (first 2 byte used for bad marker) */
    if (oob)
        rt_memcpy(w25n01gv_spare_buf + 26, oob, oob_len);

    /* step1: fetch page address */
    w25n01gv_uint16_t block_page_addr;
    int rslt;
    rslt = w25n01gv_page_addr_from_page_index((w25n01gv_uint32_t)nand_chunk, &block_page_addr);
    if (!rslt)
        return YAFFS_FAIL;

    /* wait idle */
    w25n01gv_wait_idle();

    /* step2: load user data program data */
    rslt = w25n01gv_program_data_load(0, data, data_len, 1);
    if (!rslt)
        return YAFFS_FAIL;

    /* step3: load spare area program data */
    rslt = w25n01gv_program_data_load(W25N01GV_DATA_BYTES_PER_PAGE, w25n01gv_spare_buf, sizeof(w25n01gv_spare_buf), 1);
    if (!rslt)
        return YAFFS_FAIL;

    /* step4: program */
    w25n01gv_program_execute(block_page_addr);
    return YAFFS_OK;
}

static int yaffs_w25n01gv_read_chunk(struct yaffs_dev *dev, int nand_chunk,
                                     u8 *data, int data_len,
                                     u8 *oob, int oob_len,
                                     enum yaffs_ecc_result *ecc_result_out)
{
    int rslt;
    enum yaffs_ecc_result ecc_result;
    int i;
    u8 *e;
    u8 read_ecc[3];

    /* step1: calculate page address from absolute page index */
    w25n01gv_uint16_t block_page_addr;
    rslt = w25n01gv_page_addr_from_page_index((w25n01gv_uint32_t)nand_chunk, &block_page_addr);
    if (!rslt) {
        if (ecc_result_out)
            *ecc_result_out = YAFFS_ECC_RESULT_UNKNOWN;
        return YAFFS_FAIL;
    }

    /* wait idle */
    w25n01gv_wait_idle();

    /* step2: send page data read command, wait W25N01GV prepare specified page data on the internal page buffer  */
    rslt = w25n01gv_paga_data_read(block_page_addr);
    if (!rslt) {
        if (ecc_result_out)
            *ecc_result_out = YAFFS_ECC_RESULT_UNKNOWN;
        return YAFFS_FAIL;
    }

    /* wait idle */
    w25n01gv_wait_idle();

    /* step3: send page read command, read user data from W25N01GV page buffer */
    rslt = w25n01gv_read(0, data, data_len);
    if (!rslt) {
        if (ecc_result_out)
            *ecc_result_out = YAFFS_ECC_RESULT_UNKNOWN;
        return YAFFS_FAIL;
    }

    /* step4: send page read command, read spare area from W25N01GV page buffer */
    rslt = w25n01gv_read(W25N01GV_DATA_BYTES_PER_PAGE, w25n01gv_spare_buf, sizeof(w25n01gv_spare_buf));
    if (!rslt) {
        if (ecc_result_out)
            *ecc_result_out = YAFFS_ECC_RESULT_UNKNOWN;
        return YAFFS_FAIL;
    }

    /* Do ECC and marshalling */
    if (oob)
        memcpy(oob, w25n01gv_spare_buf + 26, oob_len);

    ecc_result = YAFFS_ECC_RESULT_NO_ERROR;

    if (data) {
        for (i = 0, e = w25n01gv_spare_buf + 2; i < chip->data_bytes_per_page; i += 256, e += 3) {
            yaffs_ecc_calc(data + i, read_ecc);
            rslt = yaffs_ecc_correct(data + i, e, read_ecc);
            if (rslt < 0)
                ecc_result = YAFFS_ECC_RESULT_UNFIXED; /* error unfixed */
            else if (rslt > 0 && ecc_result == YAFFS_ECC_RESULT_NO_ERROR)
                ecc_result = YAFFS_ECC_RESULT_FIXED; /* error fixed */
        }
    }

    if (ecc_result_out)
        *ecc_result_out = ecc_result;

    return YAFFS_OK;
}

static int yaffs_w25n01gv_erase_block(struct yaffs_dev *dev, int block_no)
{
    /* step1: calculate block address from absolute block index */
    w25n01gv_uint16_t block_page_addr = (block_no * W25N01GV_PAGES_PER_BLOCK) << W25N01GV_PAGE_ADDR_LEN;

    /* wait idle */
    w25n01gv_wait_idle();

    /* step2: send block erase command */
    w25n01gv_block_erase(block_page_addr);

    return YAFFS_OK;
}

/// @brief mark first page spare area first two bytes as 'Y' if bad block
/// @param dev
/// @param block_no absolute block index
/// @return
static int yaffs_w25n01gv_mark_bad(struct yaffs_dev *dev, int block_no)
{
    /* step1: calculate first chunk address for a block */
    w25n01gv_uint16_t block_page_addr = (block_no * W25N01GV_PAGES_PER_BLOCK) << W25N01GV_PAGE_ADDR_LEN;

    /* step2: write first two byte in spare area as 'Y' to mak as bad block */
    rt_memset(w25n01gv_spare_buf, 0xff, sizeof(w25n01gv_spare_buf));
    w25n01gv_spare_buf[0] = 'Y';
    w25n01gv_spare_buf[1] = 'Y';

    /* wait idle */
    w25n01gv_wait_idle();

    /* step3: load program data */
    int rslt = w25n01gv_program_data_load(W25N01GV_DATA_BYTES_PER_PAGE, w25n01gv_spare_buf, 2, 1);
    if (!rslt)
        return YAFFS_FAIL;

    /* step4: program, assume block has been erased */
    w25n01gv_program_execute(block_page_addr);

    return YAFFS_OK;
}

/// @brief check if block is bad
/// @param dev
/// @param block_no
/// @return 0 block is bad, 1 block is normal
static int yaffs_w25n01gv_check_bad(struct yaffs_dev *dev, int block_no)
{
    /* step1: calculate first chunk address for a block */
    w25n01gv_uint16_t block_page_addr = (block_no * W25N01GV_PAGES_PER_BLOCK) << W25N01GV_PAGE_ADDR_LEN;

    /* step2: read page to W25N01GV internal page buffer, wait to be read out  */
    int rslt = w25n01gv_paga_data_read(block_page_addr);
    if (!rslt)
        return YAFFS_FAIL;

    /* wait idle */
    w25n01gv_wait_idle();

    /* step3: send page read command, read spare area from W25N01GV page buffer */
    rslt = w25n01gv_read(W25N01GV_DATA_BYTES_PER_PAGE, w25n01gv_spare_buf, sizeof(w25n01gv_spare_buf));
    if (!rslt)
        return YAFFS_FAIL;

    /* step4: check that bad block marker is not set */
    if (yaffs_hweight8(w25n01gv_spare_buf[0]) + yaffs_hweight8(w25n01gv_spare_buf[1]) < 14)
        return YAFFS_FAIL;

    return YAFFS_OK;
}

static int yaffs_w25n01gv_initialise(struct yaffs_dev *dev)
{
    board_w25n01gv_init((enum w25n01gv_spi_mode)hard);
    return YAFFS_OK;
}

static int yaffs_w25n01gv_deinitialise(struct yaffs_dev *dev)
{
    return YAFFS_OK;
}

static char *yaffs_w25n01gv_name;

struct yaffs_dev *yaffs_w25n01gv_install_drv(const char *name)
{
    struct yaffs_dev *dev = NULL;
    struct yaffs_param *param;
    struct yaffs_driver *drv;

    dev = rt_malloc(sizeof(*dev));

    if (!dev)
        return NULL;

    rt_memset(dev, 0, sizeof(*dev));
    yaffs_w25n01gv_name = rt_malloc(rt_strlen(name) + 1);
    rt_memcpy(yaffs_w25n01gv_name, name, rt_strlen(name) + 1);
    dev->param.name = yaffs_w25n01gv_name;

    if (!dev->param.name) {
        rt_free(dev);
        return NULL;
    }

    drv = &dev->drv;

    drv->drv_write_chunk_fn  = yaffs_w25n01gv_write_chunk;
    drv->drv_read_chunk_fn   = yaffs_w25n01gv_read_chunk;
    drv->drv_erase_fn        = yaffs_w25n01gv_erase_block;
    drv->drv_mark_bad_fn     = yaffs_w25n01gv_mark_bad;
    drv->drv_check_bad_fn    = yaffs_w25n01gv_check_bad;
    drv->drv_initialise_fn   = yaffs_w25n01gv_initialise;
    drv->drv_deinitialise_fn = yaffs_w25n01gv_deinitialise;

    param = &dev->param;

    w25n01gvzeig_deivce_info device_info;
    fetch_w25n01gvzeig_device_info(&device_info);

    param->total_bytes_per_chunk = device_info.data_bytes_per_page;
    param->chunks_per_block      = device_info.spare_bytes_per_page;
    param->start_block           = YAFFS_USE_W25N01GV_BLOCK_START_INDEX;
    param->end_block             = YAFFS_USE_W25N01GV_BLOCK_END_INDEX;
    param->is_yaffs2             = YAFFS_USE_YAFFS2;
    param->use_nand_ecc          = YAFFS_USE_W25N01GV_HARD_ECC;

    param->n_reserved_blocks    = YAFFS_USE_W25N01GV_RESERVED_BLOCKS;
    param->wide_tnodes_disabled = YAFFS_WIDE_TNODES_DISABLE;
    param->refresh_period       = YAFFS_REFRESH_PERIOD_MS;
    param->n_caches             = YAFFS_WRITE_CACHE_MAX; // Use caches

    param->enable_xattr = YAFFS_ENABEL_XATTR;

    /* dev->driver_context is not used by this simulator */

    yaffs_add_device(dev);

    return dev;
}